iT邦幫忙

2023 iThome 鐵人賽

DAY 26
1
Web 3

淺談ZK Rollup系列 第 26

Day 26 - ZK Rollup SP03: Geth進階操作(智能合約)

  • 分享至 

  • xImage
  •  

昨天講述了怎麼使用Go-ethereum對鏈上進行交易,還不太清楚的可以點這邊觀看,而今天則是要介紹該怎麼使用Go-ethereum與鏈上的合約做操作,我們都知道要與鏈上合約做操作的時候,主要需要兩個東西,一個就是合約的地址,另一個是合約的ABI,因為要跟鏈上合約互動,等於是做一個收款人地址是合約地址的交易,而要呼叫合約哪個函數則是要透過交易帶入的額外資料來決定,至於怎麼編碼跟解碼那些資料則需要靠合約的ABI。
透過昨天的教學,我們已經會做一般鏈上的交易了,所以意味著我們只要能填入NewTransaction裡面的data就可以與合約互動了,而我們發現在"github.com/ethereum/go-ethereum/accounts/abi"在這裡面有提供將ABI編碼的東西,但是每次都還要按照合約的格式定義出他每個不同函式的型別,相當麻煩,但是沒有關係,Go-ethereum這邊有提供一個相當好用的工具叫做Abigen,當我們在安裝Go-ethereum時,有順便安裝Developer Tools的話,這個工具就包含在裡面了。

Abigen

Abigen是其中一個由Go-ethereum提供的開發工具之一,他的功用是可以把智能合約的ABI檔轉換成Golang的程式包,他可以把智能合約轉換成一個struct,你只要把他新增出來,就可以很好的對鏈上合約進行操作,假設你能額外提供智能合約的二進位檔,他還可以幫你輕鬆部屬智能合約到鏈上,那該怎麼做呢?接下來我一步一步帶著你們做。
首先,要先有一個智能合約,我為了示範,就隨便寫一個檔名叫做test.sol的智能合約出來,智能合約內容如下:

// SPDX-License-Identifier: MIT
//
pragma solidity ^0.8.0;

contract MyContract {
    address private owner;
    uint256 number;
    uint256 private amount;
    event Receipt(address indexed sender, uint256 indexed amount);

    constructor(uint256 _number) {
        owner = msg.sender;
        number = _number;
        amount = 0;
    }

    function getNumber() external view returns (uint256) {
        return number;
    }

    function getAmount() external view returns (uint256) {
        return amount;
    }

    function donate() external payable {
        emit Receipt(msg.sender, msg.value);
        amount += msg.value;
    }

    function withdraw(uint256 value) external {
        require(msg.sender == owner);
        require(amount >= value);
        amount -= value;
        payable(msg.sender).transfer(value);
    }

    function changeNumber(uint256 newNumber) external {
        number = newNumber;
    }
}

有了這個合約之後,需要有他的ABI,如果需要部署他,還需要有他的二進位檔,因此我們需要Solidity的編譯器將我們所需要的東西編譯出來,這邊可以用線上版本的Remix IDE或者是安裝solc來進行編譯,因為我的作業系統是Windows,安裝solc相當麻煩,但是我可以用solc在npm版本的輕量版本的solc,如果需要安裝,則需要有Node.js環境,在command line下以下指令:

$ npm install solc

如果電腦作業系統是Linux或者Mac,可以使用以下指令安裝solc:

$ sudo apt-get install

這兩個不同的點在於,如果是使用前者的話,指令就不可以打solc,而是要打solcjs,兩者在功能上略有不同,我是用前者做示範。
有了Solidity的編譯器後,下指令分別得到他的二進位檔跟ABI,分別下以下兩個指令:

$ solcjs --abi test.sol
$ solcjs --bin test.sol

這時候會得到兩個檔案分別是test_sol_MyContract.abi跟test_sol_MyContract.bin,有了這兩個之後,就可以使用Abigen來產生一個Golang的程式碼檔案,在command line下以下指令:

$ abigen --bin="test.bin" --abi="abi.json" --pkg="contracts" --out="contract.go" --type="MyContract"

這邊帶入參數說明如下:

  • --bin
    • 帶入剛剛編譯出來的二進位檔,如果沒有要部屬合約這個可以不用。
  • --abi
    • 帶入剛剛編譯出來的ABI。
  • --pkg
    • 編譯出來的Golang程式的package名稱。
  • --out
    • 編譯出來的Golang程式的檔案名稱。
  • --type
    • 編譯出來的Golang程式對該合約的struct名稱

輸入完指令後,可以發現生成一個檔名為contract.go,因為我將他的package名稱設為contracts而非main,所以要新增一個名叫contracts的資料夾給他。接著只要在主程式中import contracts這個package就可以使用MyContract這個型別了。

部屬合約

當我們仔細觀察contract.go這支檔案,可以發現裡面有個函式叫做DeployMyContract,詳細內容如下:

func contracts.DeployMyContract(auth *bind.TransactOpts, backend bind.ContractBackend, _number *big.Int) (common.Address, *types.Transaction, *contracts.MyContract, error)

可以發現他需要帶入三個參數分別是auth、backend跟_number,如果說夠仔細的話,可以發現這個_number就是我合約中的建構子所要帶入的參數,所以這個函式就是在部屬合約的,只要在程式中呼叫他,就可以幫你部屬合約,而這邊的backend要帶入的就是昨天講到連線的client,至於auth就是要用要部屬的帳戶的私鑰來生成,這個auth主要是用來做簽章的,而要新增一個TransactOpts型別的變數,靠的是"github.com/ethereum/go-ethereum/accounts/abi/bind"裡面提供的func bind.NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*bind.TransactOpts, error)一定要用帶有chainID的這個,原因昨天也說過,現在以太坊交易簽章都是走EIP-155,這是要有chainId的,透過這種方式就可以拿到他需要帶入的TransactOpts型別的變數,都用好後,他會回傳部署上去的合約地址,這個東西要記錄下來,還有這次的交易,以及一個MyContract的東西,和error,如果部署完後想要直接跟合約互動,可以直接使用那個回傳回來的MyContract型別的變數,但這邊我只講部屬所以這東西還沒有用處。
程式碼如下:

package main

import (
	"context"
	"fmt"
	"math/big"
	"專案名稱/contracts"

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

func main() {
	client, err := ethclient.Dial("連線網址,如果是要連你的私有鏈的話,就是 http://127.0.0.1:[你設定的PORT]")
	if err != nil {
		panic(err)
	}
	chainID, err := client.NetworkID(context.TODO())
	if err != nil {
		panic(err)
	}
	deployer_prk, err := crypto.HexToECDSA("要部屬合約的帳戶私鑰16進位數字碼(不含0x字首)")
	if err != nil {
		panic(err)
	}
	auth, err := bind.NewKeyedTransactorWithChainID(deployer_prk, chainID)
	if err != nil {
		panic(err)
	}
	addr, _, _, err := contracts.DeployMyContract(auth, client, big.NewInt(12)) // 這邊_number我是隨便帶等等會用
	if err != nil {
		panic(err)
	}
    // 輸出合約地址
	fmt.Println(addr.Hex())
}

合約互動

剛剛說到如果合約部署的時候,會給一個MyContract型別的變數,基本上跟合約互動都是透過那個變數,如果不是部屬合約的程式,那麼就要透過contract.go裡面的函式func contracts.NewMyContract(address common.Address, backend bind.ContractBackend) (*contracts.MyContract, error)來新增出一個合約的變數,要帶入的參數分別是合約地址與連線的client,而這邊變數就可以隨便你用了,你可以發現他已經定義好原本你合約裡面寫好的所有公開的function,而這邊要提醒的是,如果是呼叫view function的話,bind.CallOpts這裡就可以不用帶,這個的功能跟剛剛部署合約的一模一樣,主要是用來簽章用的,以下是我寫出一個小程式跟我示範的合約做互動:

package main

import (
	"context"
	"fmt"
	"math/big"
	"專案名稱/contracts"

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

func main() {
	client, err := ethclient.Dial("連線網址,如果是要連你的私有鏈的話,就是 http://127.0.0.1:[你設定的PORT]")
	if err != nil {
		panic(err)
	}
	chainID, err := client.NetworkID(context.TODO())
	if err != nil {
		panic(err)
	}
	deployer_prk, err := crypto.HexToECDSA("部屬合約的帳戶私鑰16進位數字碼(不含0x字首)")
	if err != nil {
		panic(err)
	}
	auth, err := bind.NewKeyedTransactorWithChainID(deployer_prk, chainID)
	if err != nil {
		panic(err)
	}
	contract, err := contracts.NewMyContract(common.HexToAddress("合約的地址(含0x字首)"), client)
	if err != nil {
		panic(err)
	}
	number, err := contract.GetNumber(nil)
	if err != nil {
		panic(err)
	}
	fmt.Println(number)
	_, err = contract.ChangeNumber(auth, big.NewInt(567))
	if err != nil {
		panic(err)
	}
}

以上是使用得到合約裡面的number,以及修改合約的number做示範,如果是遇到payable的function,則需要在auth裡面的Value更改成要付出的金額,當然單位是wei,而這個MyContract型別的變數可以透過Filter來聽取鏈上該合約發出的Event,這也是一個常用的功能,以上便是使用Go-ethereum搭配Abigen做出可以與合約互動的程式的介紹。


上一篇
Day 25 - ZK Rollup SP02: Geth基本操作(交易)
下一篇
Day 27 - ZK Rollup SP03: ZK Rollup SP03: gnark
系列文
淺談ZK Rollup30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
雷N
iT邦研究生 1 級 ‧ 2023-10-12 23:19:02

abigen好東西

真的超讚~

我要留言

立即登入留言